import {
    AmbientLight,
    BoxGeometry,
    DirectionalLight,
    GridHelper, Material, Mesh, MeshBasicMaterial,
    MeshLambertMaterial,
    PerspectiveCamera,
    Scene, Texture, TextureLoader,
    Vector3,
    WebGLRenderer,
} from 'three';
import {TrackballControls} from 'three/examples/jsm/controls/TrackballControls';

export class CubeRender {
    // 渲染器
    public renderer: WebGLRenderer;

    // 场景
    public scene: Scene;

    // 相机
    public camera: PerspectiveCamera;

    // 控制器
    public controls: TrackballControls;

    public cubeMesh: Mesh;

    public cameraTarget: Vector3;

    private aspectRatio = 1;
    private cameraDefaults = {
        position: new Vector3(30, 15, 30),
        targetPosition: new Vector3(0, 0, 0),
        near: 0.01,
        far: 1000,
        fov: 45,
    };

    constructor(private canvas: HTMLCanvasElement) {
        this.cameraTarget = this.cameraDefaults.targetPosition;
    }

    public initDisplay(): void {
        this.initRenderer();
        this.initScene();
        this.initCamera();
        this.initControl();
        this.initLight();

        // GridHelper
        // const helper = new GridHelper(40, 40, 0xFF4444, 0x404040);
        // this.scene.add(helper);
    }

    /**
     * 重新计算画布比例
     */
    public reCalcAspectRatio(): void {
        this.aspectRatio = (this.canvas.clientWidth === 0) ? 1 : this.canvas.clientWidth / this.canvas.clientHeight;
    }

    /**
     * 调整画面
     */
    public resizeDisplay(): void {
        this.controls.handleResize();
        this.reCalcAspectRatio();
        this.renderer.setSize(this.canvas.offsetWidth, this.canvas.offsetHeight, false);
        this.updateCamera();
    }

    public render(): void {
        if (!this.renderer.autoClear) {
            this.renderer.clear();
        }

        this.controls.update();
        this.renderer.render(this.scene, this.camera);
    }

    public updateCamera(): void {
        this.camera.aspect = this.aspectRatio;

        // 摄像机注视方位
        this.camera.lookAt(this.cameraTarget);

        // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用
        this.camera.updateProjectionMatrix();
    }

    public resetCamera(): void {
        this.camera.position.copy(this.cameraDefaults.position);
        this.cameraTarget.copy(this.cameraDefaults.targetPosition);
        this.updateCamera();
    }

    public initRenderer(): void {
        this.renderer = new WebGLRenderer({
            canvas: this.canvas,
            antialias: true,
            alpha: true,
        });
    }

    public initScene(): void {
        this.scene = new Scene();
    }

    public initCamera(): void {
        this.camera = new PerspectiveCamera(
            this.cameraDefaults.fov,
            this.aspectRatio,
            this.cameraDefaults.near,
            this.cameraDefaults.far
        );
        this.resetCamera();
    }

    public initControl(): void {
        this.controls = new TrackballControls(this.camera, this.renderer.domElement);
        // 是否禁止缩放
        this.controls.noZoom = false;
        // 是否禁止旋转
        this.controls.noRotate = true;
        // 是否禁止平移
        this.controls.noPan = true;
    }

    public initLight(): void {
        const ambient = new AmbientLight('#ffffff');
        this.scene.add(ambient);

        const directionalLight = new DirectionalLight('#ffffff');
        directionalLight.position
            .set(1, 0.75, 0.5)
            .normalize();
        this.scene.add(directionalLight);
    }

    public async initCube(): Promise<void> {
        const cube = new BoxGeometry(10, 10, 10);
        let cubeMaterial: Material;

        try {
            const texture = await this.loadTexture();
            cubeMaterial = new MeshBasicMaterial({
                map: texture,
            });
        } catch (e) {
            console.error(e);
            cubeMaterial = new MeshLambertMaterial({
                color: '#2184d0',
            });
        }

        this.cubeMesh = new Mesh(cube, cubeMaterial);
        this.cubeMesh.position.set(0, 0, 0);
        this.scene.add(this.cubeMesh);
    }

    private loadTexture(): Promise<Texture> {
        return new Promise((resolve, reject) => {
            new TextureLoader().load(
                'assets/image/crate.gif',
                (res) => {
                    resolve(res);
                },
                () => {},
                (e) => {
                    reject(e);
                }
            );
        })
    }
}
